package org.example.core.impl;

import com.alibaba.druid.support.json.JSONUtils;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.*;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.delegate.ExecutionListener;
import org.activiti.engine.delegate.TaskListener;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.io.FileUtils;
import org.example.core.IWorkFlowService;
import org.example.listener.FlowEndListener;
import org.example.listener.TaskCompleteListener;
import org.example.listener.TaskCreateListener;
import org.example.model.WfDef;
import org.example.model.WfTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 工作流服务实现
 *
 * @author wanghuidong
 * @date 2024/7/1 16:12
 */
@Service
public class WorkFlowServiceImpl implements IWorkFlowService {

    private final static Logger logger = LoggerFactory.getLogger(WorkFlowServiceImpl.class);

    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    @Autowired
    private TaskService taskService;

    /**
     * 动态创建和部署工作流
     */
    @Override
    public String createWorkflowAndDeploy(WfDef wfDef) {
        // 创建一个新的BPMN模型
        BpmnModel bpmnModel = new BpmnModel();
        org.activiti.bpmn.model.Process process = new Process();
        bpmnModel.addProcess(process);
        process.setId(wfDef.getId());
        process.setName(wfDef.getName());

        // 添加开始事件
        StartEvent startEvent = new StartEvent();
        startEvent.setId("START");
        startEvent.setName("开始");
        process.addFlowElement(startEvent);

        // 添加用户任务
        List<WfTask> wfTasks = wfDef.getWfTasks();
        for (WfTask wfTask : wfTasks) {
            UserTask userTask = createUserTask(wfTask.getId(), wfTask.getName(), wfTask.getAssignee());
            process.addFlowElement(userTask);
        }

        // 添加结束事件
        EndEvent endEvent = createEndEvent("END", "结束");
        process.addFlowElement(endEvent);

        // 添加顺序流转
        List<String> flowIds = new ArrayList<>();
        SequenceFlow startFlow = new SequenceFlow("START", wfTasks.get(0).getId());
        startFlow.setId("flow0");
        process.addFlowElement(startFlow);
        flowIds.add(startFlow.getId());
        for (int i = 0; i < wfTasks.size(); i++) {
            WfTask wfTask = wfTasks.get(i);
            String nextNodeId;
            if (i < wfTasks.size() - 1) {
                nextNodeId = wfTasks.get(i + 1).getId();
            } else {
                nextNodeId = "END";
            }
            SequenceFlow sequenceFlow = new SequenceFlow(wfTask.getId(), nextNodeId);
            sequenceFlow.setId("flow" + (i + 1));
            if (i > 0) {
                sequenceFlow.setName("通过");
                sequenceFlow.setConditionExpression("${pass==true}");
            }
            process.addFlowElement(sequenceFlow);
            flowIds.add(sequenceFlow.getId());
        }

        // 添加驳回流转,第一个任务是提交审批，从第二个任务开始有驳回操作
        List<SequenceFlow> rejectFlows = new ArrayList<>();
        for (int i = 1; i < wfTasks.size(); i++) {
            WfTask wfTask = wfTasks.get(i);
            SequenceFlow rejectFlow = new SequenceFlow(wfTask.getId(), wfTasks.get(0).getId());
            rejectFlow.setId("rejectFlow" + i);
            rejectFlow.setName("驳回");
            rejectFlow.setConditionExpression("${pass==false}");
            process.addFlowElement(rejectFlow);
            rejectFlows.add(rejectFlow);
        }

        List<String> taskNodeIds = wfTasks.stream().map(x -> x.getId()).collect(Collectors.toList());
        addBpmnDiInfo(bpmnModel, taskNodeIds, flowIds, rejectFlows);


        // 将BPMN模型转换为XML格式
        BpmnXMLConverter bpmnXMLConverter = new BpmnXMLConverter();
        byte[] bpmnBytes = bpmnXMLConverter.convertToXML(bpmnModel);


        // 将BPMN XML保存到文件系统
        String fileName = wfDef.getId() + ".bpmn20.xml";
        try {
            FileUtils.writeByteArrayToFile(new File(fileName), bpmnBytes);
        } catch (IOException ex) {
            ex.printStackTrace();
        }

        // 部署BPMN模型
        Deployment deployment = repositoryService.createDeployment()
                .addBytes(fileName, bpmnBytes)
                .name("Dynamic Process Deployment")
                .deploy();
        return deployment.getId();
    }

    public Task findTask(String taskId) {
        return taskService.createTaskQuery().taskId(taskId).singleResult();
    }

    public ProcessInstance findFlowInstance(String taskId) {
        Task task = this.findTask(taskId);
        if (task != null) {
            return runtimeService.createProcessInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .singleResult();
        }
        return null;
    }

    public List<String> getOutgoingFlows(String taskId) {
        // 通过 TaskRuntime 获取当前任务所在的流程实例
        Task task = taskService.createTaskQuery().taskId(taskId).singleResult();

        // 从流程定义中获取与当前任务相关的流程图信息
        BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
        FlowElement currentFlowElement = bpmnModel.getFlowElement(task.getTaskDefinitionKey());

        // 获取当前任务节点的出线，即下一个节点
        List<SequenceFlow> outgoingFlows = ((FlowNode) currentFlowElement).getOutgoingFlows();
        List<String> nextNodeIds = new ArrayList<>();
        for (SequenceFlow sequenceFlow : outgoingFlows) {
            nextNodeIds.add(sequenceFlow.getTargetRef());
        }
        return nextNodeIds;
    }

    @Override
    public void complete(String taskId, Map<String, Object> variables, Map<String, Object> transientVariables) {
        taskService.complete(taskId, variables, transientVariables);
    }

    @Override
    public void cancel(@NonNull String flowNo, String deleteReason) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceBusinessKey(flowNo).singleResult();
        if (null != processInstance) {
            runtimeService.deleteProcessInstance(processInstance.getProcessInstanceId(), deleteReason);
        }
    }

    @Override
    public String startFlow(@NonNull String flowDefNo, @Nullable Map<String, Object> param) {
        String serialId = UUID.randomUUID().toString();
        startFlow(flowDefNo, serialId, param);
        return serialId;
    }

    @Override
    public void startFlow(@NonNull String flowDefNo, @NonNull String flowNo, @Nullable Map<String, Object> param) {
        if (null == param) {
            param = new HashMap<>();
        }
        ProcessInstance processInstance = runtimeService.createProcessInstanceBuilder()
                .processDefinitionKey(flowDefNo)
                .businessKey(flowNo)
                .transientVariables(param).start();
        logger.info("启动流程[{}]，序列号[{}]，参数为[{}]", processInstance.getProcessDefinitionName(), flowNo, JSONUtils.toJSONString(param));
    }


    private UserTask createUserTask(String id, String name, String assignee) {
        UserTask userTask = new UserTask();
        userTask.setId(id);
        userTask.setName(name);
        userTask.setAssignee(assignee);

        // 添加任务创建事件监听器
        ActivitiListener createListener = new ActivitiListener();
        createListener.setEvent(TaskListener.EVENTNAME_CREATE);
        createListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
        createListener.setImplementation(TaskCreateListener.class.getName());
        userTask.getTaskListeners().add(createListener);

        // 添加任务完成事件监听器
        ActivitiListener completeListener = new ActivitiListener();
        completeListener.setEvent(TaskListener.EVENTNAME_COMPLETE);
        completeListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
        completeListener.setImplementation(TaskCompleteListener.class.getName());
        userTask.getTaskListeners().add(completeListener);

        return userTask;
    }


    private EndEvent createEndEvent(String id, String name) {
        EndEvent endEvent = new EndEvent();
        endEvent.setId(id);
        endEvent.setName(name);

        // 添加执行结束事件监听器
        ActivitiListener endListener = new ActivitiListener();
        endListener.setEvent(ExecutionListener.EVENTNAME_END);
        endListener.setImplementationType(ImplementationType.IMPLEMENTATION_TYPE_CLASS);
        endListener.setImplementation(FlowEndListener.class.getName());
        endEvent.getExecutionListeners().add(endListener);
        return endEvent;
    }


    private static void addBpmnDiInfo(BpmnModel model, List<String> taskNodeIds, List<String> flowIds, List<SequenceFlow> rejectFlows) {
        double x = 100;
        double y = 100;
        double startEndWidth = 30;
        double startEndHeight = 30;
        double taskWidth = 100;
        double taskHeight = 60;
        double gap = 100;
        double startX = x;
        double startEndY = y + (taskHeight - startEndHeight) / 2.0;
        //画节点
        model.addGraphicInfo("START", createGraphicInfo(startX, startEndY, startEndWidth, startEndHeight));
        x = x + startEndWidth + gap;
        for (String taskNodeId : taskNodeIds) {
            model.addGraphicInfo(taskNodeId, createGraphicInfo(x, y, taskWidth, taskHeight));
            x = x + taskWidth + gap;
        }
        model.addGraphicInfo("END", createGraphicInfo(x, startEndY, startEndWidth, startEndHeight));

        //画前进线
        double point1X = startX + startEndWidth;
        double pointY = y + taskHeight / 2.0;
        double point2X = point1X + gap;
        for (String flowId : flowIds) {
            model.addFlowGraphicInfoList(flowId, createFlowGraphicInfo(point1X, pointY, point2X, pointY));
            point1X = (point2X + taskWidth);
            point2X = (point1X + gap);
        }

        //画后退线
        int rejectHeightGap = 30;
        int i = 0;
        for (SequenceFlow rejectFlow : rejectFlows) {
            String fromTaskNodeId = rejectFlow.getSourceRef();
            String toTaskNodeId = rejectFlow.getTargetRef();
            int formIndex = taskNodeIds.indexOf(fromTaskNodeId);
            int toIndex = taskNodeIds.indexOf(toTaskNodeId);
            double x1 = getRejectStartX(startEndWidth, taskWidth, gap, startX, formIndex);
            double y1 = y + taskHeight;
            double x2 = x1;
            double y2 = y1 + 30 + i * rejectHeightGap;
            double x4 = getRejectEndX(startEndWidth, taskWidth, gap, startX, toIndex, taskNodeIds.size());
            double y4 = y1;
            double x3 = x4;
            double y3 = y2;
            model.addFlowGraphicInfoList(rejectFlow.getId(), createFlowGraphicInfo(x1, y1, x2, y2, x3, y3, x4, y4));
            i++;
        }
    }

    private static double getRejectStartX(double startEndWidth, double taskWidth, double gap, double startX, int taskIndex) {
        return startX + startEndWidth + (taskIndex + 1) * gap + taskIndex * taskWidth + taskWidth / 2.0;
    }

    private static double getRejectEndX(double startEndWidth, double taskWidth, double gap, double startX, int taskIndex, int taskSize) {
        if (taskIndex != -1) {
            //打回到指定任务
            return getRejectStartX(startEndWidth, taskWidth, gap, startX, taskIndex);
        } else {
            //打回到结束
            return startX + startEndWidth + (taskSize + 1) * gap + taskSize * taskWidth + startEndWidth / 2.0;
        }
    }

    private static GraphicInfo createGraphicInfo(double x, double y, double width, double height) {
        GraphicInfo graphicInfo = new GraphicInfo();
        graphicInfo.setX(x);
        graphicInfo.setY(y);
        graphicInfo.setWidth(width);
        graphicInfo.setHeight(height);
        return graphicInfo;
    }

    private static List<GraphicInfo> createFlowGraphicInfo(double... waypoints) {
        List<GraphicInfo> flowGraphicInfoList = new ArrayList<>();
        for (int i = 0; i < waypoints.length; i += 2) {
            GraphicInfo graphicInfo = new GraphicInfo();
            graphicInfo.setX(waypoints[i]);
            graphicInfo.setY(waypoints[i + 1]);
            flowGraphicInfoList.add(graphicInfo);
        }
        return flowGraphicInfoList;
    }

}
